﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.IO;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using System.Reflection;
using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Document;
using MahApps.Metro.Controls;
using MahApps.Metro.Controls.Dialogs;
using System.Text.RegularExpressions;
using Microsoft.Win32;
using HmmmIDE.Intellisense;
using System.Diagnostics;
using HmmmIDE.Properties;
using System.Windows.Controls;
using HmmmIDE.Syntax;

namespace HmmmIDE
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow :  MetroWindow
    {

        #region Field
        /// <summary>
        /// Code completion window
        /// </summary>
        CompletionWindow _completionWindow;
        /// <summary>
        /// Assembly to get resource
        /// </summary>
        Assembly _myAssembly;
        /// <summary>
        /// Regular expression for testing whether the auto complete should stop popping registers
        /// </summary>
        private static Regex _inputNumber;

        public static readonly RoutedUICommand About = new RoutedUICommand("About Box", "About", typeof(MainWindow));
        public static readonly RoutedUICommand RunPython = new RoutedUICommand("Run Code via Python", "RunPython", typeof(MainWindow));
        public static readonly RoutedUICommand RunIDE = new RoutedUICommand("Run Code", "RunIDE", typeof(MainWindow));

        private bool _isChanged = false;

        private string WinTitle = "untitled.hmmm";

        private List<ICompletionData> _commandCommpletionData;
        private List<ICompletionData> _registerCompletionData;

        private string _filePath = "";

        /// <summary>
        /// Update title with file name and change mark
        /// </summary>
        /// <param name="name">file name</param>
        /// <param name="change">True for indicating changed document.</param>
        private void UpdateTitle(string name, bool change)
        {
            if (name.Length == 0)
            {
                this.Title = WinTitle + (change ? "*" : "") + " - HMMM IDE";
            }
            else
            {
                this.WinTitle = name;
                this.Title = name + (change ? "*" : "") + " - HMMM IDE";
            }
        }
        /// <summary>
        /// Indicates whether the document has been changed
        /// </summary>
        private bool IsChanged
        {
            get
            {
                return _isChanged;
            }
            set
            {
                if(!_isChanged && value)
                {
                    UpdateTitle("", true);
                    _isChanged = value;
                }
                else if(_isChanged && !value)
                {
                    UpdateTitle("", false);
                    _isChanged = value;
                }
            }
        }

        private RegisterTracker _registerTracker;

        #endregion


        #region Constructor
        /// <summary>
        /// Loading logic for MainWindow
        /// </summary>
        public MainWindow()
        {
            // Load UI
            InitializeComponent();

            // Load assembly to get resource
            _myAssembly = Assembly.GetExecutingAssembly();

            // Load syntax
            SyntaxManager.Load();

            // Load syntax high light rules
            LoadHightLightRule();

            // Manage events
            hmmmEditor.TextArea.TextEntering += TextArea_TextEntering;
            hmmmEditor.TextArea.TextEntered += TextArea_TextEntered;
            hmmmEditor.TextArea.TextEntered += IsCodeChanged;

            // Initialize regex for test whether should input number
            _inputNumber = new Regex(@"n\s{1,}r\d{1,}\s*,\s*");

            // focus the text editor
            hmmmEditor.Focus();

            // add register tracker
            this._registerTracker = new RegisterTracker(hmmmEditor);

            // update title
            UpdateTitle("untitled.hmmm", false);


            // Load completion data
            this._registerCompletionData = new List<ICompletionData>();
            for (int i = 0; i < 16; i++)
                _registerCompletionData.Add(new AutoCompletionData(string.Format("r{0}", i), "", ""));

            this._commandCommpletionData = new List<ICompletionData>();
            foreach (var kvp in SyntaxManager.CommandDescription)
                _commandCommpletionData.Add(new AutoCompletionData(kvp.Key, SyntaxManager.CommandDescription[kvp.Key], SyntaxManager.CommandUsage[kvp.Key]));

           // Load recent files and remove invalid one
            //if (Settings.Default.RecentFiles == null) Settings.Default. = new System.Collections.Specialized.StringCollection();
            //var recentFlils = Settings.Default.RecentFiles;
            //var invalidPath = new List<string>();
            //foreach (var str in recentFlils) 
            //    if (!File.Exists(str)) 
            //        invalidPath.Add(str);
            //foreach (var str in invalidPath) recentFlils.Remove(str);
            //Settings.Default.Save();
            //foreach(var path in recentFlils)
            //{
            //    MenuItem item = new MenuItem() { Header = path };
            //    item.Click += new RoutedEventHandler(delegate(object sender, RoutedEventArgs e)
            //        {
            //            using (TextReader r = new StreamReader(item.Header.ToString()))
            //            {
            //                this.hmmmEditor.Text = r.ReadToEnd();
            //            }
            //            UpdateTitle(System.IO.Path.GetFileName(item.Header.ToString()), false);
            //            this._filePath = item.Header.ToString();
            //            IsChanged = false;
            //        })
            //        ;
            //    this.RecentFles.Items.Add(item);
            //}

            // WILL IMPROVE IT LATTER

            
        }

       

        #endregion

        #region Document logic

        void IsCodeChanged(object sender, TextCompositionEventArgs e)
        {
            this.IsChanged = true;
        }

        void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
        {
            if (e.Text == " ") // open the completion table if the user press space
            {
                // get the current line to pop up different thing
                var currentLine = hmmmEditor.Document.GetLineByOffset(hmmmEditor.CaretOffset);
                string currentText = hmmmEditor.Document.GetText(currentLine);

                if (IsInComment())
                    return;


                bool isRegister = false;
                foreach (var key in SyntaxManager.CommandDescription.Keys)
                    if (currentText.Contains(key))
                        isRegister = true;

                
                _completionWindow = new CompletionWindow(hmmmEditor.TextArea);
                _completionWindow.AllowsTransparency = true;
                IList<ICompletionData> data = _completionWindow.CompletionList.CompletionData;


                if (isRegister)
                {
                    
                    if (ShouldInputNumber(currentText))
                    {
                        _completionWindow = null;
                        return;
                    }
                    else
                    {
                        foreach (var r in this._registerCompletionData)
                            data.Add(r);
                    }

                }
                else
                {
                    foreach (var c in this._commandCommpletionData)
                        data.Add(c);

                }

                _completionWindow.Show();
                _completionWindow.Closed += delegate
                {
                    _completionWindow = null;
                };
            }

        }

        void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
        {
            if (e.Text.Length > 0 && _completionWindow != null)
            {
                if(e.Text[0] == ' ' || e.Text[0] == ',')
                {
                    _completionWindow.CompletionList.RequestInsertion(e);
                }
            }
        }

        /// <summary>
        /// Return true if the program should allow input number
        /// </summary>
        /// <param name="text">string to match</param>
        /// <returns></returns>
        private bool ShouldInputNumber(string text)
        {
            return _inputNumber.IsMatch(text);
        }

        /// <summary>
        /// Return true if the user is input comment
        /// </summary>
        /// <returns>true if there is a # before input</returns>
        private bool IsInComment()
        {
            int caretPosition = hmmmEditor.CaretOffset - 1;

            int lineOffset = hmmmEditor.Document.GetOffset(hmmmEditor.Document.GetLocation(caretPosition));

            string text = hmmmEditor.Document.GetText(lineOffset, 1);

            var currentLine = hmmmEditor.Document.GetLineByOffset(hmmmEditor.CaretOffset);

            int start = currentLine.Offset != 0? currentLine.Offset : 1;    // Dirty way to make it work

            while(lineOffset >= start)
            {
                if(text.Equals("#"))    // Look for comment sign
                {
                    return true;
                }
                text = hmmmEditor.Document.GetText(--lineOffset, 1);
            }

            return false;
        }

        #endregion

        #region Load I/O
        private void LoadHightLightRule()
        {
            using (Stream s = _myAssembly.GetManifestResourceStream("HmmmIDE.Syntax.SyntaxHighLight.xml"))
            {
                XmlTextReader reader = new XmlTextReader(s);
                hmmmEditor.SyntaxHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);

            }
        }


        #endregion






        #region File I/O
        private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            PromptChange();
            UpdateTitle("untitled.hmmm", false);
            this.hmmmEditor.Text = "";
            this._filePath = string.Empty;
        }

        private void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            PromptChange();

            OpenFileDialog open = new OpenFileDialog() { Filter = "Hmmm File|*.hmmm|Text File|*.txt" };
            if(open.ShowDialog() == true)
            {
                using (TextReader r = new StreamReader(open.FileName))
                {
                    this.hmmmEditor.Text = r.ReadToEnd();
                }
                UpdateTitle(System.IO.Path.GetFileName(open.FileName), false);
                this._filePath = open.FileName;
            }

            IsChanged = false;
        }

        /// <summary>
        /// Prompt dialog and let the user to save their document
        /// </summary>
        private bool PromptChange()
        {
            if (this.IsChanged)
            {
                var result = MessageBox.Show("Code has not been saved. Do you want to save your code?", "Warning", MessageBoxButton.YesNoCancel);
                if ( result == MessageBoxResult.Yes)
                {
                    this.SaveCommand_Executed(null, null);
                    return true;
                }
                else if(result == MessageBoxResult.No)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            return true;
        }

        private void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (String.IsNullOrEmpty(this._filePath))
            {
                SaveFileDialog save = new SaveFileDialog() { Filter = "Hmmm File|*.hmmm|Text File|*.txt" };
                if (save.ShowDialog() == true)
                {
                    WriteCodeToFile(save.FileName);
                }
            }
            else
            {
                WriteCodeToFile(this._filePath);
            }

            this.IsChanged = false;
        }

        private void WriteCodeToFile(string path)
        {
            using (TextWriter w = new StreamWriter(path, false))
            {
                w.Write(this.hmmmEditor.Text);
            }

            UpdateTitle(System.IO.Path.GetFileName(path), false);
            this._filePath = path;
        }

        private void SaveAsCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            SaveFileDialog save = new SaveFileDialog() { Filter = "Hmmm File|*.hmmm|Text File|*.txt" };
            if (save.ShowDialog() == true)
            {
                WriteCodeToFile(save.FileName);
            }
            this.IsChanged = false;
        }

        private void CommandClose(object sender, ExecutedRoutedEventArgs e)
        {
            if (PromptChange())
            {
                this.Close();
            }
        }

        #endregion


        #region Debugger

        private void RunCodeViaPython(object sender, ExecutedRoutedEventArgs e)  // Run the code from a temp file
        {
            Assembly myAssembly = Assembly.GetExecutingAssembly();
            string path = myAssembly.Location;
            path = System.IO.Path.GetDirectoryName(path);
            string exePath = System.IO.Path.Combine(path, "HmmmConsole.exe");
            if(!File.Exists(exePath))   // Console not found
            {
                this.ShowMessageAsync("Warning", "HmmmConsole.exe not found. Running aborted.", MessageDialogStyle.Affirmative);
                return;
            }
            string tempPath = System.IO.Path.GetTempFileName(); // pass the temp file to console
            ProcessStartInfo start = new ProcessStartInfo();
            start.FileName = exePath;
            start.Arguments = tempPath;
            using(TextWriter w = new StreamWriter(tempPath))
            {
                w.Write(hmmmEditor.Document.Text);
            }

            Process.Start(start);
        }

        private void RunCodeViaIDE(object sender, ExecutedRoutedEventArgs e)  // Run the code from IDE
        {
            // some dirty calls. Need to be fixed
            RegisterTracker tracker = new RegisterTracker(this.hmmmEditor);
            tracker.UpdateRegisterNumber();

            Debug.Debugger debugger = new Debug.Debugger(tracker.InstructionList.ToArray(), this.hmmmEditor);
            this.hmmmEditor.IsReadOnly = true;
            debugger.Show();

        }

        #endregion


        #region UI

        private void AboutDialog(object sender, ExecutedRoutedEventArgs e)
        {
            string message = String.Format("HMMM IDE: \nVersion: {0}\nWriter: Keyi Zhang", _myAssembly.GetName().Version);
            this.ShowMessageAsync("About", message, MessageDialogStyle.Affirmative);
        }


        private void MetroWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (!PromptChange())
                e.Cancel = true;
        }

#endregion
    }
}
